﻿using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Web;
using System.Web.Security;
using Jayden.Dll.Core.Membership;

namespace Jayden.Dll.Web
{
	public class Uri : ICloneable
	{
		private static readonly char[] m_Separators = new char[] { '/' };

		private string m_Protocol = "http";
		private string m_Host;
		private int? m_Port = null;
		private List<string> m_Path;
		private NameValueCollection m_QueryString;
		//private string m_Anchor;

		public Uri(string uri, bool htmlDecode)
		{
			if (htmlDecode && uri != null)
				uri = HttpUtility.HtmlDecode(uri);
			System.Uri sysUri = new System.Uri(uri);
			AssignFromUri(sysUri);
		}

		public Uri(System.Uri uri)
		{
			AssignFromUri(uri);
		}

		public Uri(Uri clone)
		{
			m_Protocol = clone.m_Protocol;
			m_Host = clone.m_Host;
			m_Port = clone.Port;
			if (clone.m_Path != null)
				m_Path = new List<string>(clone.m_Path);
			if(clone.m_QueryString != null)
				m_QueryString = new NameValueCollection(clone.m_QueryString);
		}

		public Uri(System.Uri baseUri, string uri, bool htmlDecode) : this(new Uri(baseUri), uri, htmlDecode) { }
		public Uri(Uri baseUri, string uri, bool htmlDecode)
		{
			if (htmlDecode && uri != null)
				uri = HttpUtility.HtmlDecode(uri);
			System.Uri sysUri = new System.Uri(new System.Uri(baseUri.ToString()), uri);
			AssignFromUri(sysUri);
		}

		public bool HasSameParameters(Uri uri)
		{
			for (int c = 0; c < uri.m_QueryString.Count; c++)
			{
				string key = uri.m_QueryString.GetKey(c);
				string value = uri.m_QueryString[c];
				if (m_QueryString[key] != value)
					return false;
			}
			return true;
		}

		protected void AssignFromUri(System.Uri uri)
		{
			m_Protocol = uri.Scheme;
			m_Host = uri.Host;
			m_Port = uri.IsDefaultPort ? null : (int?)uri.Port;
			m_Path = new List<string>(uri.AbsolutePath.Split(m_Separators, StringSplitOptions.RemoveEmptyEntries));
			GenerateQueyString(uri.Query);
		}
		protected void GenerateQueyString(string query)
		{
			m_QueryString = new NameValueCollection();
			if (query.StartsWith("?"))
				query = query.Substring(1);
			if (!string.IsNullOrWhiteSpace(query))
			{
				string[] keyVals = query.Split(new char[] { '&' }, StringSplitOptions.RemoveEmptyEntries);
				foreach (string keyVal in keyVals)
				{
					int index = keyVal.IndexOf('=');
					if (index < 0)
						m_QueryString.Add(keyVal, "");
					else
					{
						string key = HttpUtility.UrlDecode(keyVal.Substring(0, index));
						string value = HttpUtility.UrlDecode(keyVal.Substring(index + 1));
						m_QueryString.Add(key, value);
					}
				}
			}
		}
		public void ClearQueryString() { m_QueryString.Clear(); }
		public string this[string key]
		{
			get { return m_QueryString[key]; }
			set
			{
				if (value == null)
					m_QueryString.Remove(key);
				else
					m_QueryString[key] = value;
			}
		}

		public string Protocol { get { return m_Protocol; } }
		public string Host { get { return m_Host; } }
		public int? Port { get { return m_Port; } }
		public string GetQuery(bool htmlDecode)
		{
			List<string> values = new List<string>();
			for (int c = 0; c < m_QueryString.Count; c++)
				values.Add(string.Format("{0}={1}", HttpUtility.UrlEncode(m_QueryString.GetKey(c)), HttpUtility.UrlEncode(m_QueryString.Get(c))));
			if (values.Count == 0)
				return string.Empty;
			return "?" + string.Join(htmlDecode ? "&" : "&amp;", values);
		}
		public string Query
		{
			get
			{
				// by default we html encode & as it should be inside an html document according to the specs
				return GetQuery(false);
			}
		}
		public string Lang { get { return this["lang"]; } set { this["lang"] = value; } }
		public string AbsolutePath
		{
			get
			{
				if (m_Path == null)
					return string.Empty;
				string path = string.Join("/", m_Path);
				if (!path.StartsWith("/"))
					path = "/" + path;
				return path;
			}
			set
			{
				string[] paths = null;
				if (value != null)
					paths = value.Split('/');
				if (paths != null && paths.Length > 0)
					m_Path = new List<string>(paths);
				else
					m_Path = null;
			}
		}

		public void MergeQuery(Uri uri)
		{
			for (int c = 0; c < uri.m_QueryString.Count; c++)
			{
				string key = uri.m_QueryString.GetKey(c);
				string value = uri.m_QueryString.Get(c);
				m_QueryString[key] = value;
			}
		}

		public string ToAbsoluteString(bool htmlDecode)
		{
			string path = string.Format("{0}{1}", AbsolutePath, GetQuery(htmlDecode));
			return path;
		}
		public string ToRelativeString(Uri uri, bool htmlDecode)
		{
			if (uri.m_Protocol != m_Protocol)
				return ToString();
			if (uri.m_Port != m_Port || uri.m_Host != m_Host)
				return ToString();
			int index = 0;
			if(m_Path != null && uri.m_Path != null)
				while (index < m_Path.Count && index < uri.m_Path.Count && m_Path[index] == uri.m_Path[index])
					index++;
			List<string> folders = new List<string>();
			for (int c = index + 1; c < uri.m_Path.Count; c++)
				folders.Add("..");
			if(m_Path != null)
				for (int c = index; c < m_Path.Count; c++)
					folders.Add(m_Path[c]);
			return string.Join("/", folders) + GetQuery(htmlDecode);
		}
		/// <summary>
		/// Get a string representation of the url, this is a macro for ToString(true)
		/// This function should not be used to render an html document (as it does give an html decoded value)
		/// </summary>
		/// <returns>url html decoded</returns>
		public override string ToString() { return ToString(true); }
		public string ToString(bool htmlDecode)
		{
			string domain = m_Host;
			if (m_Port != null)
				domain += ":" + m_Port;
			string url = string.Format("{0}://{1}{2}", m_Protocol, domain, ToAbsoluteString(htmlDecode));
			return url;
		}

		object ICloneable.Clone() { return new Uri(this); }
		public Uri Clone() { return new Uri(this); }
		public override int GetHashCode()
		{
			return ToString().GetHashCode();
		}
		public override bool Equals(object obj)
		{
			if(obj is Uri)
				return ToString().Equals(obj.ToString());
			return false;
		}
	}


	public class UriAccess
	{
		private bool? m_Authenticated;
		private string[] m_Roles;

		public UriAccess(bool? authenticated, params string[] roles)
		{
			m_Authenticated = authenticated;
			m_Roles = roles;
		}

		public bool HaveAccess(HttpHandlerContext context)
		{
			User user = null;
			if (context == null)
				user = Membership.GetUser() as User;
			else
				user = context.User;
			if (m_Authenticated.HasValue)
			{
				if (user == null && m_Authenticated.Value)
					return false;
				if (user != null && !m_Authenticated.Value)
					return false;
			}
			if (m_Roles != null && m_Roles.Length > 0)
			{
				if (user == null)
					return false;
				foreach (string role in m_Roles)
					if (!user.RoleNames.Contains(role))
						return false;
			}
			return true;
		}

		public bool? Authenticated { get { return m_Authenticated; } }
		public IEnumerable<string> Roles { get { return m_Roles; } }
	}

}
